תוכנית הקורס cs, Technion 2..3.4 מבני נתונים בסיסיים וסימונים אסימפטוטיים מערכים ורשימות מקושרות עצים ועצי חיפוש עצי AVL עצי 2-3 עצי דרגות.5 רשימות דילוגים סיבוכיות משוערכת.6.7.8.9.0..3.4 מטרת הקורס: מבני נתונים טבלאות ערבול אחזקת קבוצות זרות מיון מיון טיפול במחרוזות גרפים איסוף אשפה הרצאת חזרה Lectue of Geige & Itai s slide bochue www.cs.technion.ac.il/~dang/couseds Geige & Itai, 202..3 הכרות עם מבני נתונים ומימושיהם היעילים פיתוח כלים לניתוח יעילות בחירת מבני נתונים לפתרון בעיות מבני נתונים: שימושים: מחסנית, תור, מילון, תור עדיפויות, טבלת ערבול... מיון, מימוש שפות תכנות, ארגון קבצים, ועוד ועוד ועוד. ספר הלימוד העיקרי (קיים גם תרגום):.Comen, Leiseson, Rivest, to Algoithms (MIT Pess) הגדרות בסיסיות מבנה נתונים הוא אוסף של פעולות על קבוצת נתונים. מימוש של מבנה נתונים הוא אוסף פרוצדורות, אחת לכל פעולה, המממשות את הפעולות של מבנה הנתונים. מבני נתונים בסיסיים וסימונים אסימפטוטיים מהי מחסנית? האם היא מערך עם מציין ל-? top 8 7 6 5 4 3 2 0 top לא! אתחול: הוספת איבר: ראש המחסנית: הוצאת איבר: זהו מימוש של מחסנית באמצעות מערך ומצביע. top = 0 top = top + ; A[top] = x; A[top] top = top cs, Technion 4 חומר קריאה לשיעור זה Chapte 2 Gowth of functions (23-4) Chapte 4 Recuences (53 60) Chapte. Stacks and Queues (200 204) cs, Technion 3
מחסנית כמבנה נתונים (המשך) מחסנית כמבנה נתונים פעולות המחסנית מקיימות את הכללים הבאים: אחרון נכנס ראשון יוצא Last In -- Fist Out : LIFO ceate(s); push(s,7); pop(s); pint is_empty(s);. אפשר לבצע pop, top רק על מחסנית לא ריקה. מיד לאחר,ceate(S) is_empty(s) מחזיר ערך.tue לאחר ביצוע,push ואחריו,pop המחסנית לא משתנה. כל מימוש חייב לאפשר את הפעולות ולקיים את הכללים. יש טענות הנובעות רק מהכללים, בלי תלות במימוש..3 לדוגמא: מה יודפס? מחסנית מוגדרת ע"י הפעולות הבאות: מחזיר מחסנית S ריקה חדשה. ceate(s) S. למחסנית x מכניס איבר בעל ערך push(s,x) מחזיר את האיבר שבראש המחסנית S (המחסנית אינה משתנה). top(s) S. מוציא את האיבר שבראש המחסנית pop(s) מחזיר tue אם המחסנית S ריקה ו- false אחרת. is_empty(s) cs, Technion 6 cs, Technion 5 איבר הכנסת מימושי מחסנית void push (STACK *S, DATA_TYPE x){ NODE *P; P = malloc (sizeof (NODE)); P info = x; P next = *S ; *S = P ; *s פעולת :push(s,x) s מימוש בעזרת מערך (כפי שהוסבר). מימוש בעזרת רשימה מקושרת: typedef stuct node { DATA_TYPE info; stuct node *next; NODE;. הגדרת צומת: typedef NODE* STACK; p x void ceate (STACK *S) { *S = NULL ; יצירת מחסנית ריקה: :Ceate(S) cs, Technion 8 cs, Technion 7
תור כמבנה נתונים אחזור והוצאת איבר ראשון נכנס ראשון יוצא Fist In -- Fist Out : FIFO תור מוגדר ע"י הפעולות הבאות: מחזיר תור ריק. מחזיר את ערך האיבר שבראש התור Q (התור אינו משתנה). מכניס איבר עם ערך x לסוף התור Q. מוציא את האיבר שבראש התור Q. ceate(q) head(q) enqueue(q,x) dequeue(q) *s void pop ( STACK *S){ STACK t ; t = (*S) next; fee (*S) ; *S = t ; פעולת :pop(s) is_empty(q) מחזיר tue אם התור Q ריק ו- false אחרת. cs, Technion 0 DATA_TYPE top ( STACK *S){ etun (*S) info ; פעולת :top(s) cs, Technion 9 מימוש של תור ע"י רשימה מקושרת מימוש של תור בעזרת מערך fist fist last הכנסה בסוף התור (last) : last last head(q): Q[f]; enqueue(q,x): Q[] = x; = (+) %n; dequeue(q): f = (f+) % n; is_empty(q): f = = ; ceate(q): f = = 0; מערך בן איברים עם שני מציינים = מציין את מקום האיבר שאחרי סוף התור = f מציין את מקום האיבר שבראש התור 8 7 6 5 4 3 2 0 f הוצאה מראש התור.(fist) שימו לב שלא ניתן להוציא איבר באמצעות המצביע.last cs, Technion 2 3 2 0 הפעולות האריתמטיות נעשות.mod n המימוש מלא אם. f = = ( +)% n יש לבדוק כי מתקיים "לא מלא" לפני enqueue ו"לא ריק" לפני.dequeue מלא אנימציה f f,f cs, Technion
זמן ריצה של אלגוריתם הערות לגבי מבני נתונים המשתמש במבנה: מכיר את הפעולות והשפעתן על הנתונים. אינו נדרש להכיר את פרטי המימוש. זמן ריצה של אלגוריתם עבור קלט יסומן ב- (). זמן הריצה נמדד ע"י מספר פקודות מכונה שהאלגוריתם מבצע על קלט נתון. מדד זה מתעלם מהבדלי המהירות בין הפקודות. (למשל, חבור לעומת כפל). הגודל של קלט יסומן ב-. לדוגמא, בתוכנית המסכמת איברי מערך, גודל הקלט הוא מספר איברי המערך. זמן הריצה הגרוע ביותר case) (wost של אלגוריתם עבור קלט שגודלו מוגדר ע"י. () = max { () = sum = 0 fo (i = 0; i < n; i++) sum = sum + a[i]; דוגמא: זמן הריצה הגרוע ביותר של אלגוריתם זה מקיים: + + 2 כאשר, 2 הם קבועים התלויים במימוש הפקודות בשפת מכונה. cs, Technion 4 בזמן פיתוח: ניתן להחליף את מימוש מבני הנתונים מבלי לפגוע בשימושים. מאפשר לתכנת מימושים פשוטים ואחר כך להחליפם. ב-++ C הדבר קל במיוחד: במקום להגדיר טיפוסים מופשטים (ADT) ב- C כפי שעשינו בשברי הקוד עד כה, ניתן לכתוב מספר מחלקות המממשות את הטיפוס המופשט, ובעת שינוי ממשק פשוט לשנות את ההגדרה (typedef) של הטיפוס המופשט. לדוגמא: // typedef AayBasedStack Stack; typedef ListBasedStack Stack; איכות המימוש נקבעת ע"י: ניתוח יעילות: זמן מספר צעדי החישוב הנדרשים, לכל פעולה. מקום כמות הזיכרון הנדרשת. פשטות התכנות (מאפשר אחזקה יעילה). cs, Technion 3 טענה: עבור קבוע מתקיים ) = + + + + = ( הוכחה: נמצא קבועים 0,0 < 0 כך שלכל יתקיים. + + + + = יהי. = max {, 0,.., אזי דוגמאות פולינומיאליות = 2 / סיבוכיות והסימון הגדרה: יהיו () (), פונקציות חיוביות. נאמר שהפונקציה () נמצאת בקבוצת הפונקציות (()) אם קיימים קבועים 0,0 < 0 כך שלכל מתקיים: () כמו כן נאמר ש- () מהווה חסם עליון אסימפטוטי לפונקציה () ונסמן זאת ע"י (()) () = במקום הסימון הרגיל (()). נכון לכל 2 לפיכך נבחר = 2 0 וכן. = 2 () () () = 0006 = () () = 4 + 27 = () () = log 2 + 3 2 = ( 2 ) עוד דוגמאות: קבוע: ליניארי: ריבועי: 0 () = (()) הדוגמא האחרונה נובעת מאי השוויון log 2 < עבור. cs, Technion 6 נשתמש בסימון זה כאשר הפונקציה () היא פונקציה שקשה לתאר במדויק, למשל זמן הריצה של אלגוריתם, בעוד () פשוטה יותר לתיאור. cs, Technion 5
דוגמאות שליליות דוגמאות נוספות () = 3 = (2 ) () = = ( ) ( קבוע ) האם מתקיים? לוגריתמי לכל ו - קבועים מתקיים: () = = ( ) וזאת מכיוון שמתקיים. = () = log = (log log ) () = = ( ε ) לכל חיובי (שברים ושלמים) מתקיים: בין ליניארי וריבועי וזאת כיוון שמתקיים התשובה שלילית בשלושת המקרים. נוכיח את המקרה הראשון. נניח בשלילה שקיימים קבועים, 0 < 0 כך שלכל 0 תקיים:.3 2 אבל אי שוויון זה אינו מתקיים עבור. > log / סתירה. () = 8 + 5 + 9 log 2 = ( log 2 ) עבור () 32 log 2 = + = ( ) ( > ) () = = ( ) ( ) אקספוננציאלי cs, Technion 8 cs, Technion 7 סיבוכיות זמן (המשך) זמן סיבוכיות דוגמא שלישית: חיפוש בינרי של במערך ממוין בן איברים. בכל צעד משווים את לאיבר האמצעי במערך העכשווי. אם האיבר האמצעי שווה ל- החיפוש נגמר. אם האיבר האמצעי גדול מ- ממשיכים עם חלק המערך המכיל את המספרים הקטנים. אם האיבר האמצעי קטן מ- ממשיכים עם חלק המערך המכיל את המספרים הגדולים. ניתוח זמן הריצה: נסמן ב- את סיבוכיות הזמן כתלות ב-. מתקיימת משוואת הנסיגה הבאה: = = + ( /2 ) דוגמא ראשונה: סכום איברי מערך. ראינו שמתקיים 2 ולפיכך. דוגמא שניה: כפל מטריצות ריבועיות בגודל. גודל הקלט. 2 2 בהנחה ש- חזקה שלמה של 2 נקבל: = + + 4 = + + + + 2 = = + + + + 2 = log + = (log ) הטענה נכונה גם ללא ההנחה על. פרטים על שיטות פתרון למשוואות נסיגה ניתן למצוא בספר הלימוד ובתרגולים. cs, Technion 20 מחשבים 2 איברים במטריצת התוצאה, כאשר כל איבר מחושב לפי ההגדרה:,,, סיבוכיות הזמן כפונקציה של היא. 3 סיבוכיות הזמן כפונקציה של גודל הקלט,, היא, / כיוון שמתקיים: / קבוע 2 / / cs, Technion 9
ln סיבוכיות זמן (המשך) בדוגמא בשקף הקודם קיבלנו: 2 3 2 3 טענה: ln הוכחה:. = ( ) דוגמא רביעית: אנליזה גסה: אנליזה עדינה: כאשר =,, = 2, = 3, = סיבוכיות זמן (המשך) S = 0; fo ( i = ; i < n; i ++) fo ( j = 0; j< n; j + = i ) S++ ; שתי לולאות מקוננות. מתבצעות פעולות. /2 /3 / = ln 2 3 2 3 ln + + + + = + + + + וסה"כ: = נקרא המספר ההרמוני ה- cs, Technion 22 cs, Technion 2 חסם תחתון אסימפטוטי הגדרה: יהיו () (), פונקציות חיוביות. נאמר שהפונקציה () נמצאת בקבוצת הפונקציות Ω(()) (אומגה) אם קיימים קבועים 0,0 < 0 כך שלכל מתקיים: () () () () בדוגמא לפני שני שקפים קיבלנו: סיבוכיות זמן (המשך) + 2 + 3 + + = + 2 + 3 + + = וכיוון שמתקיים: ln() = + + + + + מכאן נקבל כי: + ln 2 ln = 2 ln = ( log ) נכון לכל 0 () = Ω(()) הערה: לשם דיוק היה עלינו לכפול את כל המשוואות בקבוע. בכל מקרה, היינו מקבלים = ( log ) קיימות הגדרות נוספות לחסם תחתון אשר שקולות להגדרה לעיל עבור פונקציות מונוטוניות. cs, Technion 24 cs, Technion 23
הסימון קטן הגדרה: יהיו () (), פונקציות חיוביות. נאמר שהפונקציה () נמצאת בקבוצת הפונקציות (()) אם לכל קבוע < 0 קיים קבוע 0 0 כך שלכל מתקיים: () () () () זניחה אסימפטוטית יחסית לפונקציה () () = Θ(()) חסם הדוק אסימפטוטי הגדרה: יהיו () (), פונקציות חיוביות. נאמר שמתקיים (תטה) אם (()) = וגם Ω(()).() = 2 () () () 0 () = Θ(()) 0 () = (()) הגדרה (אקויולנטית): נאמר שמתקיים ) = ( אם = 0 lim דוגמאות: () log = (), 00 cs, Technion 26 הגדרה (אקויולנטית): נאמר שמתקיים Θ(()) () = אם קיימים קבועים 0,0 <,0 < 2 0 כך שלכל מתקיים: 2 () cs, Technion 25 מגבלות הסימון האסימפטוטי נוח להשתמש בסימונים אסימפטוטיים מפני שהסימון מתעלם מקבועים ומאפשר ניתוח זמנים פשוט יותר. בנוסף, סימונים אלו מאפשרים לבחור אלגוריתמים ישימים יותר עבור קלטים גדולים. אנו נשתמש בסימונים אלה לאורך הקורס. אבל לסימון יש מגבלות מסוכנות ברור שנעדיף תוכנית הרצה בזמן () = 2 על פני תוכנית הרצה בזמן קבוע של 0 60 (ממומשת למשל ע"י טבלה ענקית של תשובות לכל אפשרות) כיוון שבתוכניות ממשיות אנו משתמשים בגודל קלט סופי הקטן באופן משמעותי מ- 0 80 (מספר האטומים ביקום). מסקנה: צריך לוודא שהקבועים 0, המתחבאים בהגדרות האסימפטוטיות,,Θ Ω אמנם "סבירים". דוגמא. נניח שאלגוריתם A רץ בזמן () = 00 ואילו אלגוריתם B רץ בזמן. () = 5 2 בניתוח אסימפטוטי עדיף אלגוריתם כיון שהאלגוריתם ליניארי, אבל עבור קלטים המקיימים < 2 עדיף אלגוריתם. 00 2 20 A B cs, Technion 28 cs, Technion 2 20 27